#!/bin/awk -f
#-------------------------------------------------------------------------------
#	file structmap.awk
#
#	brief
#	Create abs / offset maps for specific device driver structures
#
#	$Id: $
#-------------------------------------------------------------------------------

#-------------------------------------------------------------------------------
#	BEGIN processing
#
#	invocation looks like this:
#		awk -f structmap.awk <input file> <typedef> <output file> <stats file>
#	or
#		structmap.awk <input file> <typedef> <output file> <stats file>
#
#	Where:
#	- input file: This is the header file to be examined.
#	- typedef: This is the typedef device object to be located (e.g. LPC_ADC_T).
#	- output file: Output file to be generated.
#	- stats file: Statistics file generated. Gives information about the run.
#-------------------------------------------------------------------------------
BEGIN {
	flag_db = "false"															# init debug flag false

	if (ARGC == 5) {															# arg count must be 5
		t_test = ARGV[2]														# typedef to be located is 3rd
		f_out = ARGV[3]															# output file is 4th
		f_stat = ARGV[4]														# stats file is 5th
		if (flag_db == "true") {												# if debug flag is true
			printf "   in: %s\n",ARGV[1]										# print input file
			printf " type: %s\n",ARGV[2]										# print typedef
			printf "  out: %s\n",ARGV[3]										# print output file
			printf "stats: %s\n",ARGV[4]										# print statistics file
		}
		ARGC = 2																# set argc (awk and input file)
		flag_td = "false"														# init typedef flag false
		flag_bg = "true"														# init begin flag true
		flag_cm = "false"														# init comment flag  false
		count_un = 0															# union counter is zero
		print_header()															# print the excel header
	}
	else {																		# arg count is off
		printf "Unknown options:\n"												# print info message
		for (i in ARGV) {														# loop through the arguments
			printf "  %s: %s\n",i,ARGV[i]										# display the values of each
		}																		# end else
		flag_bg = "false"														# begin flag is false (failed clause)
		exit																	# exit
	}																			# end if
}																				# end begin

#-------------------------------------------------------------------------------
#	Comment / white-space processing
#-------------------------------------------------------------------------------
$0 ~ /^[\r\n]+$/ {																# if the line is blank...
	if (flag_db == "true")														# if debug flag is true
		print "blank line"														# print status
	next																		# move on to the next line
}

$0 ~ /\/\// {																	# match single line comments "//"
	next																		# no further processing
}																				# end match clause

$0 ~ /^\/\*/ {																	# match multi-line comments (start of line) "/*"
	flag_cm = "true"															# set the comment flag
}																				# end match clause

flag_cm == "true" {																# if in a comment
	if (flag_db == "true")														# if debug flag is true
		printf "comment: %s\n", $0												# print the line
	if ($0 ~ /\*\//) {															# if find end comment "*/"
		flag_cm = "false"														# clear the comment flag
	}
	next																		# get the next line
}

#-------------------------------------------------------------------------------
#	Typedef processing
#-------------------------------------------------------------------------------
$1 ~ /^typedef/ {																# match "typedef" (start of line)
	stat["typedef"] += 1														# bump stats
	if (flag_td == "true") {													# if the typedef flag is already set...
		msg_exit("Typedef in typedef")											# handle typedef in typedef case (exit)
	}																			# end typedef flag test
	if ($2 ~ /struct/) {														# if the typedef is a struct
		stat["struct"] += 1														# bump stats
		flag_td = "true"														# set it true: in a typedef
		ct_element = 0															# reset element count
	}																			# end if
	if ($2 ~ /enum/) {															# if the typedef is enum
		stat["enum"] += 1														# bump stats
	}																			# end if
	next																		# get the next line
}																				# end match clause

#-------------------------------------------------------------------------------
#	union element processing
#-------------------------------------------------------------------------------
flag_td == "true" {																# if in a typedef...
	if ($1 ~ /union/) {															# if a union in a typedef...
		count_un += 1															# bump union counter
		next																	# we're in a union!
	}																			# end union test

	if (count_un != 0) {														# if we are in a union
		if ($1  ~ /^};/) {														# if first field is "};"...
			count_un -= 1														# remove one level of unions
			var_size[ct_element-1] = "1"										# set last union element size to 1
			next																# get the next line
		}

		if ($1 !~ /^__/) {														# look for __ on first element (e.g. __IO, __I)
			msg_warn("Misformed union element")									# handle mis-formed element (warning)
			next																# get next line
		}

		var_proc[ct_element] = "union"											# union item
		var_type[ct_element] = process_var_type()								# process the union element type
		var_name[ct_element] = process_var_name()								# extract the union element variable name
		var_size[ct_element] = "0"												# set union size (always 0)
		stat["union"] += 1														# bump statistics
		ct_element += 1															# bump the element count
		next																	# get the next line
	}

#-------------------------------------------------------------------------------
#	item output processing
#-------------------------------------------------------------------------------
	if ($1  ~ /^}/) {															# if first field is "}"...
		t_def = $2																# typedef is the second field
		gsub(/[;\r\n]/,"",t_def)												# remove semicolon and line ending
		if (t_def == t_test) {
			for (i=0; i<ct_element; i++) {										# loop through the elements
				if (var_proc[i] == "structure") {								# print structure item
					printf "%s,",	var_proc[i]	> f_out							# print item base type
					printf "%s,",	var_type[i]	> f_out							# print item type and type size
					printf "%s,",	var_name[i]	> f_out							# print item name
					printf "%s\n",	var_size[i] > f_out							# print item overall size
				}

				if (var_proc[i] == "union") {									# print union item
					printf "%s,",	var_proc[i]	> f_out							# print item base type
					printf "%s,",	var_type[i]	> f_out							# print item type and type size
					printf "%s,",	var_name[i]	> f_out							# print item name
					printf "%s\n",	var_size[i] > f_out							# print item overall size
				}

				if (var_proc[i] == "pre-processor") {							# print pre-processor item
					printf "%s,",	var_proc[i]	> f_out							# print item base type
					printf "%s\n",	var_name[i]	> f_out							# print item name
				}
			}
			exit																# script is done, exit
		}
		flag_td = "false"														# exit typedef state
		next
	}

#-------------------------------------------------------------------------------
#	structure element processing
#-------------------------------------------------------------------------------
	if ($1 ~ /^\#/) {
		var_proc[ct_element] = "pre-processor"									# pre-processor element
		var_type[ct_element] = "pre-processor,0"								# save preprocessor as type
		var_name[ct_element] = $0												# save entire line
		var_size[ct_element] = "0"												# save as overall size
		stat["pre-proc"] += 1													# bump stats
		ct_element += 1															# bump the element count
		next																	# get the next line...
	}
	else {																		# if the line is not a pre-processor directive...
		if ($1 !~ /^__/) {														# look for __ on first element (e.g. __IO, __I)
			print $0
			msg_warn("Misformed structure element")								# handle mis-formed structure element
			next																# next line...
		}
	                                                                            
		var_proc[ct_element] = "structure"										# structure item
		var_type[ct_element] = process_var_type()								# process the structure element type
		var_name[ct_element] = process_var_name()								# extract the structure element variable name
		var_size[ct_element] = process_var_size(var_name[ct_element])			# extract the structure element variable name
		stat["struct"] += 1														# bump stats
		ct_element += 1															# bump the element count
		next																	# get the next line
	}
}

#-------------------------------------------------------------------------------
#	End of file processing
#-------------------------------------------------------------------------------
END {
	if (flag_bg == "true")														# if processing passed being state...
		print_stat()															# ...print the stats, then exit
}

#-------------------------------------------------------------------------------
#	function
#		print_header
#
#	brief
#		print output file header
#
#	no parameters, no return
#-------------------------------------------------------------------------------
function print_header()
{
	hdr = "Base,Item Type,Item Size,Item Name,Item Count,Overall Size,Offset (decimal),Offset (hex)"
	printf "%s\n",hdr > f_out													# print the header
}

#-------------------------------------------------------------------------------
#	function
#		print_stat
#
#	brief
#		print statistics in the associate array "stat"
#
#	no parameters, no return
#-------------------------------------------------------------------------------
function print_stat()
{
	print "Statistics:" > f_stat												# print the statistics header
	for (i in stat) {															# loop through all the statistics
		printf "%10s: %d\n", i, stat[i] > f_stat								# print them out
	}
}

#-------------------------------------------------------------------------------
#	function
#		process_var_type
#
#	brief
#		process item type: determine size
#		return information as a string
#
#	paramters
#		none
#
#	returns
#		string containing type and size
#-------------------------------------------------------------------------------
function process_var_type()
{
	sz = 0																		# set the type size to 0
	if ($2 ~ /uint32_t/) sz = 4													# if type is uint32_t then size is 4
	if ($2 ~ /uint16_t/) sz = 2													# if type is uint16_t then size is 2
	if ($2 ~ /uint8_t/)  sz = 1													# if type is uint8_t then size is 1
	if (sz == 0) {																# if size is 0, it's unknown
		s = sprintf("Unknown type: %s",$2)										# build exit message
		msg_exit(s)																# print the message and exit
	}
	else {
		s = sprintf("%s,%d",$2,sz)												# output size
	}
	return s																	# return the string
}

#-------------------------------------------------------------------------------
#	function
#		process_var_name
#
#	brief
#		process the item name
#		return information as a string
#
#	paramters
#		none
#
#	returns
#		item name string
#-------------------------------------------------------------------------------
function process_var_name()
{
	s = ""																		# clear out s
	if ($3 ~ /.+;/) {															# simple type: if the string ends in ";"
		s = sprintf("%s",$3)													# print the complete type
	}
	else {																		# complex type
		for (i=3; i<NF; i++) {													# loop to the end of the line (if necessary)
			s = s sprintf("%s",$i)												# print the partial type
			if ($i ~ /.+;/) break												# end of type?
		}																		# end for
	}																			# end if

	gsub(/[\r\n;]/,"",s)														# remove semicolon / line endings
	return s																	# return the string
}

#-------------------------------------------------------------------------------
#	function
#		process_var_size
#
#	brief
#		process item size
#		return overall size as a string (e.g. array)
#
#	paramters
#		none
#
#	returns
#		item size
#-------------------------------------------------------------------------------
function process_var_size(in_str)
{
	s = ""																		# clear out s
	if (in_str ~ /.+\[.+\]$/) {													# if the item ends in [...]...
		match(in_str,/\[.+\]/)													# find the array string
		arr_sz = substr(in_str,RSTART+1,RLENGTH-2)								# pull the array string
		if (arr_sz ~ /^[0-9]+$/)												# if array size is a number...
			s = arr_sz															# send it
		else																	# if not
			s = "?"																# send dummy value
	}
	else {																		# if not an array
		s = "1"																	# item ct is 1
	}
	
	return s																	# return the string
}

#-------------------------------------------------------------------------------
#	function
#		msg_exit
#
#	brief
#		print an error message with a line number and exit
#
#	paramters
#		msg: message to print
#
#	returns
#		this function exits the script - it does not return
#-------------------------------------------------------------------------------
function msg_exit(msg)
{
	printf "Exit: line: %d, msg: %s.\n",NR,msg									# print the line number, message, then...
	exit																		# ...exit
}

#-------------------------------------------------------------------------------
#	function
#		msg_warn
#
#	brief
#		print an error message with a line number
#
#	paramters
#		msg: message to print
#
#	returns
#		nothing
#-------------------------------------------------------------------------------
function msg_warn(msg)
{
	printf "Warning: line: %d, msg: %s.\n",NR,msg								# print the line number, message, then...
}

#
#	End
#
